/* Simple program:  Move N sprites around on the screen as fast as possible */

#include <cstdio>
#include <cstdlib>
#include <string>
#include <cctype>
#include <ctime>

#include <iostream>
#include <boost/smart_ptr.hpp>
#include "SEL.h"

// This is just rewritten sprite demo from the SDL.

const int NUM_SPRITES  = 100;
const int MAX_SPEED    = 1;

using namespace SEL;

Surface *sprite;
int numsprites;

Rect *sprite_rects;
Rect *positions;
Rect *velocities;
int sprites_visible;

// This function returns nothing. Instead of that on any error an exception is thrown.
void loadSprite(std::string file)
{
    // Load the sprite image
    sprite = Surface::loadBMP(file);

    // Set transparent pixel as the pixel at (0,0)
    if (sprite->format()->palette)
    {
        sprite->setColorKey(*(Uint8 *)sprite->pixels(), SDL::SRCCOLORKEY | SDL::RLEACCEL);
    }

    // Convert sprite to video format
    sprite->displayFormat();
}



void moveSprites(const boost::scoped_ptr<Display> &screen, Uint32 background)
{
    int i, nupdates;
    Rect area, *position, *velocity;

    nupdates = 0;
    // Erase all the sprites if necessary
    if (sprites_visible)
    {
        screen->fill(background);
    }

    // Move the sprite, bounce at the wall, and draw
    for (i=0; i<numsprites; ++i)
    {
        position = &positions[i];
        velocity = &velocities[i];
        position->x += velocity->x;
        if ( (position->x < 0) || (position->x >= screen->w ()) )
        {
            velocity->x = -velocity->x;
            position->x += velocity->x;
        }

        position->y += velocity->y;
        if ( (position->y < 0) || (position->y >= screen->h ()) )
        {
            velocity->y = -velocity->y;
            position->y += velocity->y;
        }

        // Blit the sprite onto the screen
        area = *position;
        screen->blit(sprite, NULL, &area);
        sprite_rects[nupdates++] = area;
    }

    // Update the screen!
    if ( (screen->flags() & SDL::DOUBLEBUF) == SDL::DOUBLEBUF )
    {
        screen->flip();
    }
    else
    {
        screen->updateRects(nupdates, sprite_rects);
    }

    sprites_visible = 1;
}


/// This is a way of telling whether or not to use hardware surfaces
Uint32 fastestFlags(Uint32 flags)
{
    // Hardware acceleration is only used in fullscreen mode
    flags |= SDL::FULLSCREEN;

    // Initialize video info
    VideoInfo::getVideoInfo();

    // Check for various video capabilities
    if (VideoInfo::blit_hw_CC() && VideoInfo::blit_fill() )
    {
        // We use accelerated colorkeying and color filling
        flags |= SDL::HWSURFACE;
    }

    // If we have enough video memory, and will use accelerated blits directly to it, then use page flipping.
    if ( (flags & SDL::HWSURFACE) == SDL::HWSURFACE )
    {
        // Direct hardware blitting without double-buffering causes really bad flickering.
        SDL::Surface *screen = SDL::GetVideoSurface();
        if ( VideoInfo::video_mem() > (Uint32)(screen->h * screen->pitch) )
        {
            flags |= SDL::DOUBLEBUF;
        }
        else
        {
            flags &= ~SDL::HWSURFACE;
        }
    }

    // Return the flags
    return(flags);
}

int main(int argc, char *argv[])
{
    Uint8 *mem;
    int width, height;
    Uint8  video_bpp;
    Uint32 videoflags;
    Uint32 background;
    int    i, done;
    Uint32 then, now, frames;

    try
    {
        // Initialize SDL
        SEL::init(INIT_VIDEO);

        atexit(SEL::quit);

        numsprites = NUM_SPRITES;
        videoflags = SDL::HWSURFACE /*| SDL::FULLSCREEN*/ | SDL::ANYFORMAT;
        width = 1024;
        height = 768;
        video_bpp = 8;
        while ( argc > 1 )
        {
            --argc;
            if ( strcmp(argv[argc-1], "-width") == 0 )
            {
                width = atoi(argv[argc]);
                --argc;
            }
            else if ( strcmp(argv[argc-1], "-height") == 0 )
            {
                height = atoi(argv[argc]);
                --argc;
            }
            else if ( strcmp(argv[argc-1], "-bpp") == 0 )
            {
                video_bpp = atoi(argv[argc]);
                videoflags &= ~SDL::ANYFORMAT;
                --argc;
            }
            else if ( strcmp(argv[argc], "-fast") == 0 )
            {
                videoflags = fastestFlags(videoflags);
            }
            else if ( strcmp(argv[argc], "-hw") == 0 )
            {
                videoflags ^= SDL::HWSURFACE;
            }
            else if ( strcmp(argv[argc], "-flip") == 0 )
            {
                videoflags ^= SDL::DOUBLEBUF;
            }
            else if ( strcmp(argv[argc], "-fullscreen") == 0 )
            {
                videoflags ^= SDL::FULLSCREEN;
            }
            else if ( isdigit(argv[argc][0]) )
            {
                numsprites = atoi(argv[argc]);
            }
            else
            {
                fprintf(stderr,
                        "Usage: %s [-bpp N] [-hw] [-flip] [-fast] [-fullscreen] [numsprites]\n",
                        argv[0]);
                exit(1);
            }
        }

        //Display *screen;

        // Set video mode
        boost::scoped_ptr<Display> screen( new Display(width, height, video_bpp, videoflags));

        // Load the sprite
        loadSprite("icon.bmp");

        // Allocate memory for the sprite info
        mem = (Uint8 *)malloc(4*sizeof(Rect)*numsprites);
        if ( mem == NULL )
        {
            delete sprite;
            std::cerr << "Out of memory !\n";
            exit(2);
        }

        sprite_rects = (Rect *)mem;
        positions = sprite_rects;
        sprite_rects += numsprites;
        velocities = sprite_rects;
        sprite_rects += numsprites;
        srand(time(NULL));
        for ( i=0; i<numsprites; ++i )
        {
            positions[i].x = rand()%screen->w ();
            positions[i].y = rand()%screen->h ();
            positions[i].w = sprite->w ();
            positions[i].h = sprite->h ();
            velocities[i].x = 0;
            velocities[i].y = 0;
            while ( ! velocities[i].x && ! velocities[i].y )
            {
                velocities[i].x = (rand()%(MAX_SPEED*2+1))
                                  - MAX_SPEED;
                velocities[i].y = (rand()%(MAX_SPEED*2+1))
                                  -MAX_SPEED;
            }
        }
        background = screen->mapRGB(0x00, 0x00, 0xFF);

        // Print out information about our surfaces
        printf("Screen is at %d bits per pixel\n", screen->format()->BitsPerPixel);

        if ( (screen->flags () & SDL::HWSURFACE) == SDL::HWSURFACE )
        {
            printf("Screen is in video memory\n");
        }
        else
        {
            printf("Screen is in system memory\n");
        }

        if ( (screen->flags () & SDL::DOUBLEBUF) == SDL::DOUBLEBUF )
        {
            printf("Screen has double-buffering enabled\n");
        }
        if ( (sprite->flags () & SDL::HWSURFACE) == SDL::HWSURFACE )
        {
            printf("Sprite is in video memory\n");
        }
        else
        {
            printf("Sprite is in system memory\n");
        }

        // Run a sample blit to trigger blit acceleration
        {
            Rect dst;
            dst.x = 0;
            dst.y = 0;
            dst.w = sprite->w ();
            dst.h = sprite->h ();
            screen->blit (sprite, NULL, &dst);
            screen->fillRect (dst, background);
        }

        if ( (sprite->flags () & SDL::HWACCEL) == SDL::HWACCEL )
        {
            printf("Sprite blit uses hardware acceleration\n");
        }
        if ( (sprite->flags () & SDL::RLEACCEL) == SDL::RLEACCEL )
        {
            printf("Sprite blit uses RLE acceleration\n");
        }

        // Loop, blitting sprites and waiting for a keystroke
        frames = 0;
        then = SDL::GetTicks();
        done = 0;
        sprites_visible = 0;
        Event event;
        while ( !done )
        {
            // Check for events
            ++frames;
            while ( event.poll () )
            {
                switch (event.type ())
                {
                case SDL::KEYDOWN:
                    // Any keypress quits the app...
                case SDL::QUIT:
                    done = 1;
                    break;
                default:
                    break;
                }
            }
            moveSprites(screen, background);
        }
        delete sprite;
        free (mem);

        // Print out some timing information
        now = SDL::GetTicks();
        if (now > then)
        {
            printf("%2.2f frames per second\n", ((double)frames*1000)/(now-then));
        }
    }
    catch (Error error)
    {
        std::cerr << "Exception caught: " << error.str () << std::endl;
    }

    return(0);
}
